﻿using System;
using System.Linq;
using System.Reflection;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Slma.OData.Factory;
// Author: Miroslav Matula m.matula@alutam.cz 
// Date : 9.9.2010
namespace Slma.OData
{
  /// <summary>
  /// Neobsahuje odkazy na jiné třídy. Změny svého stavu oznamuje přes Events: 
  /// PropertyChanged. (Datové property, IsChanged, EntityState, IsEditing)
  ///
  /// Možné stavy EntityState
  /// Undefined(po vytvoření)->New,Unmodified,Deleted,Dispose. V tomto stavu se při změně dat přepisují originální data  bez vyvolání eventu PropertyChanged.
  /// New->Unmodified,DeletedNew.
  /// Unmodified->Modified,Deleted
  /// Deleted->Unmodified,Modified
  /// DeletedNew->New
  /// 
  /// Stavové property:EntityState,IsChanged,ReadOnly
  /// </summary>
  /// <typeparam name="T_ODataEntityType"></typeparam>
  public abstract class Entity : IDisposable, INotifyPropertyChanged, IEditableObject, IRevertibleChangeTracking, IChangeTracking
  {
    #region Static

    //-- default values pro initializaci buffru -------------- 	
    public static object GetDefaultEntityValue(Type type_)
    {
      object res = null;
      if(type_.IsGenericType) res = null; // Nulable typ (jiná varianta generického typu tam bý nemůže)
      else if(type_ == typeof(string)) res = string.Empty;
      else if(type_ == typeof(byte)) res = (byte)0;
      else if(type_ == typeof(bool)) res = false;
      else if(type_ == typeof(short)) res = (short)0;
      else if(type_ == typeof(ushort)) res = (ushort)0;
      else if(type_ == typeof(int)) res = 0;
      else if(type_ == typeof(uint)) res = 0U;
      else if(type_ == typeof(long)) res = 0L;
      else if(type_ == typeof(ulong)) res = 0UL;
      else if(type_ == typeof(float)) res = 0f;
      else if(type_ == typeof(double)) res = 0d;
      else if(type_ == typeof(decimal)) res = decimal.Zero;
      else if(type_ == typeof(DateTime)) res = DateTime.MinValue;
      else if(type_ == typeof(Guid)) res = Guid.Empty;
      return res;
    }
    private static object ConvertTo(object value, Type DestinationType_)
    {
      if(DestinationType_.Equals(value.GetType())) return value;
      try {
        if(DestinationType_ == typeof(Byte)) value = Convert.ToByte(value);
        else if(DestinationType_ == typeof(sbyte)) value = Convert.ToSByte(value);
        else if(DestinationType_ == typeof(Int16)) value = Convert.ToInt16(value);
        else if(DestinationType_ == typeof(Int32)) value = Convert.ToInt32(value);
        else if(DestinationType_ == typeof(Int64)) value = Convert.ToInt64(value);
        else if(DestinationType_ == typeof(UInt16)) value = Convert.ToUInt16(value);
        else if(DestinationType_ == typeof(UInt32)) value = Convert.ToUInt32(value);
        else if(DestinationType_ == typeof(UInt64)) value = Convert.ToUInt64(value);
        else if(DestinationType_ == typeof(decimal)) value = Convert.ToDecimal(value);
        else if(DestinationType_ == typeof(Double)) value = Convert.ToDouble(value);
        else if(DestinationType_ == typeof(Single)) value = Convert.ToSingle(value);
        else if(DestinationType_ == typeof(DateTime)) value = Convert.ToDateTime(value);
      } catch {
        throw new Exception(string.Format("Can not convert type {0} to type {1}", value.GetType().Name, DestinationType_));
      }
      return value;
    }

    #endregion

    #region -- Ctor ----------------------------------------------

    public Entity()
    {
      //---- data ---------------------------------------------
      _data = new object[DefProperty._propertyList.Count]; // data kontejner pro dyn.property
      InitializeNew(); // nastavení default hodnot a stavu
    }

    #endregion ---------------------------------------------------

    protected abstract CDef DefProperty { get; }// Container Definice
    #region -- GetValue, SetValue -- !!!!!!!!!!!!!!! ---------

    // !!!! NEMĚNIT SIGNATURU !!!!!!!! na tyto metody jsou mapované z dynamicky vytvářených property 
    // základní metody pro get a set dat
    //---
    public void SetValue(int propertyNr, object value)
    {
      if(DBNull.Value.Equals(value)) value = null; // DBNull je Null
      //--
      Factory.EntityPropertyDef epd = DefProperty._propertyList[propertyNr];
      //-- Konvertovat type
      if((value!=null) && !(epd.PropertyType.Equals(value.GetType()))) value = Entity.ConvertTo(value, epd.PropertyType);
      //-- kontrola dat
      if(epd.PropertyType == typeof(string)) {
        if((value == null) && (!epd.AllowDBNull)) value = string.Empty;
        if((value != null) && (epd.MaxLength > -1)) {
          string svalue = value as string;
          if(svalue.Length > epd.MaxLength) value = svalue.Substring(0, epd.MaxLength);
        }
      }
      //-- jedná se o změnu ?
      if(value == _data[propertyNr]) return; // nejedná se o změnu
      // -- validace hodnot ----
      if(ValidatePropertyValue && epd.Validate) {
        //var ctx = new ValidationContext(this, null, null);
        EntityValidationContext.MemberName = epd.PropertyName;
        Validator.ValidateProperty(value, EntityValidationContext);
      }


      //-- Original data
      if((EntityState == EntityState.Unmodified) && (_dataOrigin == null)) {
        _dataOrigin = new object[DefProperty._propertyList.Count];
        Array.Copy(_data, _dataOrigin, _data.Length);
      }
      _data[propertyNr] = value;

      if(EntityState == EntityState.Unmodified) EntityState = EntityState.Modified;
      // když je EntityState.Undefined, pak neoznamuj změnu
      if(EntityState != EntityState.Undefined) this.OnPropertyChanged(DefProperty._propertyList[propertyNr].PropertyName);
    }
    //----
    public object GetValue(int propertyNr)
    {
      return _data[propertyNr];
    }

    #endregion

    #region -- Events ------------------------------------

    //++++++++++++++++++++++++++++++++
    public event EventHandler<EventEntityChangingArgs> EntityChanging;
    /// </summary>
    /// Uživatel může operaci zrušit Cancel=true
    /// </summary>
    /// <param name="entityStateNew_">Nový stav do kterého bude Entita přecházet</param>
    /// <returns>False - cancel operace</returns>
    protected virtual bool OnEntityChanging(NotifyEntityAction entityOperation_)
    {
      if(this.EntityChanging == null) return true;
      var arg = new EventEntityChangingArgs() { EntityAction = entityOperation_ };
      this.EntityChanging(this, arg);
      return !arg.Cancel;
    }
    //++++++++++++++++++++++++++++++++
    public event EventHandler<EventEntityChangedArgs> EntityChanged;
    protected virtual void OnEntityChanged(NotifyEntityAction entityOperation_)
    {
      if(this.EntityChanged == null) return;
      var arg = new EventEntityChangedArgs() { EntityAction = entityOperation_ };
      this.EntityChanged(this, arg);
    }
    //++++++++++++++++++++++++++++++++
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
      if(_suppressPropertyChangedCnt > 0) return;
      if(this.PropertyChanged == null) return;
      this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion

    #region -- Fields -------------------------------------------

    private object _ODataEntity; // odkaz na původní OData z LoadData
    protected object[] _data; // kontejner na data z property. Pořadí v poli je definováno v _columnList
    protected object[] _dataOrigin; // kopie dat z _data. Uchovává originalní data. 
    internal int _suppressPropertyChangedCnt = 0; // >0 zakazuje generovat eventy. Slouží pro pro back operace
    private EditSession _editSession = null; // ukládá data před zahájením operace Edit
    //private object _entityList; // List ve kterem je entita evidovana

    #endregion

    #region -- Properties ---------------------------------------

    //---
    [Display(AutoGenerateField = false)]
    public EntityState EntityState
    {
      get { return this._entityState; }
      protected internal set
      {
        if(this._entityState == value) return;
        if(this._entityState == EntityState.Disposed) return;

        bool chan = IsChanged;
        // vracím original data
        if((value == EntityState.Unmodified) && (_dataOrigin != null)) {
          _dataOrigin = null;
        }
        //
        this._entityState = value;
        this.OnPropertyChanged("EntityState");
        if(chan != IsChanged) this.OnPropertyChanged("IsChanged");
      }
    }
    protected EntityState _entityState;
    //---				
    [Display(AutoGenerateField = false)]
    public bool IsChanged { get { return this._entityState != EntityState.Unmodified && this._entityState != EntityState.Undefined; } }
    //-- stav BeginEdit --
    [Display(AutoGenerateField = false)]
    public bool IsEditing { get { return (this._editSession != null); } }
    //-- definice Entity
    //internal Factory.EntityPropertyDefList EntityPropertyDefList { get { return _cdef._propertyList; } }
    private ValidationContext _validationContextEntity;
    private ValidationContext EntityValidationContext
    {
      get
      {
        if(_validationContextEntity == null) _validationContextEntity = new ValidationContext(this, null, null);
        return _validationContextEntity;
      }
    }
    [Display(AutoGenerateField=false)]
    public bool ValidatePropertyValue { get; set; }

    #endregion

    #region -- inner Methods --------------------------------

    //------
    internal void InitializeNew()
    {
      this._entityState = EntityState.New;
      for(int i = 0; i < DefProperty._propertyList.Count; i++) {
        _data[i] = DefProperty._defaultValues[i];
      }
    }

    #endregion

    #region -- Methods ---------------------------------------

    public bool Remove()
    {
      bool res = OnEntityChanging(NotifyEntityAction.Remove);
      if(res) {
        if(this.EntityState == EntityState.New) {
          this.EntityState = EntityState.DeletedNew;
          OnEntityChanged(NotifyEntityAction.Remove);
        } else {
          this.EntityState = EntityState.Deleted;
          OnEntityChanged(NotifyEntityAction.Remove);
        }
      }
      return res;
    }
    //----
    public void SetValue(string propertyName, object value)
    {
      SetValue(DefProperty._propertyList.OrderOf(propertyName), value);
    }
    //----
    public object GetValue(int propertyNr, bool original_)
    {
      if(original_ && IsChanged && (_dataOrigin != null)) return _dataOrigin[propertyNr];
      return _data[propertyNr];
    }

    public object GetValue(string propertyName_)
    {
      EntityPropertyDef dd = DefProperty._propertyList.FirstOrDefault(p => p.PropertyName == propertyName_);
      if(dd == null) throw new Exception("Neznámý PropertyName");
      int idx = DefProperty._propertyList.IndexOf(dd);
      return GetValue(idx, false);
    }

    public Entity Clone()
    {
      Entity en = Activator.CreateInstance(this.GetType()) as Entity;
      en._data = _data;
      en._dataOrigin = _dataOrigin;
      en._entityState = _entityState;
      return en;
    }

    // nastaví data z ODataEntity
    public void SetValuesFromODataEntity(object data_)
    {
      //_ODataEntity
      _ODataEntity = data_;
      Type thisType = this.GetType();
      var propInfo =  data_.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
      foreach(var item in propInfo) {
        thisType.GetProperty(item.Name)
        .SetValue(this, item.GetValue(data_, null), null);
      }
    }

    public object GetODataEntity()
    {
      // nastavím hodnoty
      Type thisType = this.GetType();
      var propInfo = _ODataEntity.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
      foreach(var item in propInfo) {
        var pro = thisType.GetProperty(item.Name);
        var val = pro.GetValue(this, null);
        item.SetValue(_ODataEntity, val, null);
      }
      return _ODataEntity;
    }

    public object CreateODataEntity(Type ODataEntityType_)
    {
      var res = Activator.CreateInstance(ODataEntityType_);
      // nastavím hodnoty
      Type thisType = this.GetType();
      var propInfo = ODataEntityType_.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
      foreach(var item in propInfo) {
        var pro = thisType.GetProperty(item.Name);
        var val = pro.GetValue(this, null);
        item.SetValue(res, val, null);
      }
      return res;
    }

    #endregion

    #region --- NestedClasses -------------------------

    protected class CDef
    {
      internal object[] _defaultValues;
      internal Factory.EntityPropertyDefList _propertyList = null; // definice columns(datových property)
    }

    private class EditSession
    {
      public EntityState _lastEntityState;
      public object[] _data;
    }

    #endregion

    #region IEditableObject Members

    public void BeginEdit()
    {
      if(!this.IsEditing && OnEntityChanging(NotifyEntityAction.Edit)) {
        this._editSession = new EditSession();
        this._editSession._lastEntityState = this.EntityState;
        this._editSession._data = new object[DefProperty._propertyList.Count];
        Array.Copy(_data, _editSession._data, _data.Length);
        //this._editSession.DomainMethodInvocation = this.CustomMethodInvocation;
        OnPropertyChanged("IsEditing");
      }
    }

    public void EndEdit()
    {
      if(this.IsEditing) {
        this._editSession = null;
        OnPropertyChanged("IsEditing");
        OnEntityChanged(NotifyEntityAction.Edit);
      }
    }

    public void CancelEdit()
    {
      if(this.IsEditing) {
        // vracím púvodní hodnoty
        for(int i = 0; i < DefProperty._propertyList.Count; i++) {
          SetValue(i, _editSession._data[i]); // Aby se vyvolala changed property
        }
        EntityState = this._editSession._lastEntityState;
        this._editSession = null;
        OnPropertyChanged("IsEditing");
      }
    }

    #endregion

    #region IRevertibleChangeTracking Members

    public void RejectChanges()
    {
      RejectChanges(false);
    }
    //----
    public void RejectChanges(bool suppressPropertyChanged_)
    {
      if(IsEditing) CancelEdit();
      if((this.EntityState == EntityState.Unmodified) || (this.EntityState == EntityState.Disposed)) return;

      if(!OnEntityChanging(NotifyEntityAction.RejectChanges)) return; // dotaz

      lock(this) {
        if(suppressPropertyChanged_) _suppressPropertyChangedCnt++;
        // delete
        if((this.EntityState == EntityState.Deleted)) {
          EntityState = EntityState.Unmodified;
        }
        // modify
        if((this.EntityState == EntityState.Modified)) {
          if(_dataOrigin != null) {
            Array.Copy(_dataOrigin, _data, _data.Length);
            _dataOrigin = null;
            if(!suppressPropertyChanged_) {
              // vyvolat property changed
              Type thisType = this.GetType();
              var propInfo = thisType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
              foreach(var item in propInfo) {
                OnPropertyChanged(item.Name);
              }
            }
          }
          EntityState = EntityState.Unmodified;
        }
        if(suppressPropertyChanged_) {
          _suppressPropertyChangedCnt--;
        }
        OnEntityChanged(NotifyEntityAction.RejectChanges);

        // New, deletedNew
        if(this.EntityState == EntityState.DeletedNew || this.EntityState == EntityState.New) {
          this.Dispose();
        }
      }
    }



    #endregion

    #region IChangeTracking Members

    public void AcceptChanges()
    {
      AcceptChanges(true);
    }
    //----
    public void AcceptChanges(bool allowInvokePropertyChanged_)
    {
      if(this.IsEditing) CancelEdit();
      _dataOrigin = null; //zahazuji orginal data
      if((this.EntityState == EntityState.Unmodified) || (this.EntityState == EntityState.Disposed)) return;

      if(!OnEntityChanging(NotifyEntityAction.AcceptChanges)) return; // dotaz

      lock(this) {

        if(allowInvokePropertyChanged_) _suppressPropertyChangedCnt++;
        // delete
        if(this.EntityState == EntityState.DeletedNew) {
          // this.Dispose(); tot udělá ODataSourceView
        } else if((this.EntityState == EntityState.New) || (this.EntityState == EntityState.Modified)) {
          this.EntityState = EntityState.Unmodified;
        }
        if(allowInvokePropertyChanged_) _suppressPropertyChangedCnt--;
        //--
        OnEntityChanged(NotifyEntityAction.AcceptChanges);
      }
      //-- odstraním
      if(this.EntityState == EntityState.Disposed) this.Dispose();
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    {
      if(this.EntityState != EntityState.Disposed) {
        this._entityState = EntityState.Disposed;
        //OnEntityChanged(EntityOperations.AcceptChanges);
      }
      // uvolní vazby
      PropertyChanged = null; 
      EntityChanging = null;
      EntityChanged = null;
      _ODataEntity = null;
      _data = null;
      _dataOrigin = null;
      _editSession = null;
      GC.SuppressFinalize(this);
    }

    #endregion

  }
}
